package br.ufpe.cin.mac.middleware.components.concrete;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.util.Iterator;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import br.ufpe.cin.mac.middleware.components.ConcreteComponent;
import br.ufpe.cin.mac.util.Message;

/**
 * Class Decrypt
 * 
 * @author Osmany
 * @author Rafael Roque
 *
 */
public class Decrypt extends ConcreteComponent implements DecryptMBean {


	public Decrypt() {
		super(Decrypt.class.getCanonicalName());

	}


	public Message decryptograph(Message msg) {
		try {
			
			byte[] bMethod;
			byte[][] bParam;
			byte bSig[][];

			PrivateKey pvk = carregaChavePrivada(new File("chave.privada"));			

			// recuperando bMethod			
			bMethod = decifra(pvk, msg.getBMethod(), msg.getKey());
			msg.setBMethod(bMethod);

			//recuperando cada Parametro
			byte[][] cbParam = msg.getBParam();
			bParam = new byte[cbParam.length][];
			for (int i = 0; i < cbParam.length; i++) {
				bParam[i] = decifra(pvk, cbParam[i], msg.getKey());
			}
			msg.setBParam(bParam);

			//recuperando cada assinatura
			byte[][] cbSig = msg.getBSig();
			bSig = new byte[cbSig.length][];
			for (int i = 0; i < cbSig.length; i++) {
				bSig[i] = decifra(pvk, cbSig[i], msg.getKey());
			}
			msg.setBSig(bSig);

			return msg;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	protected void msgManager(Message msg) {
		List<String> ops = msg.readOperations();
		Iterator<String> it = ops.iterator();
		while (it.hasNext()) {

			String op = it.next();
			if (op.equals("sendResponse"))
				sendResponse(msg);

			else if (op.equals("sendRequest"))
				sendRequest(msg);

			else if (op.equals("Cryptography"))
				decryptograph(msg);
		}
	}

	private void sendResponse(Message msg) {
		msg = this.decryptograph(msg);
		buffer.put(msg.hashCode(), msg);
		sendNotification(msg, "terP");
	}

	private void sendRequest(Message msg) {
		msg = this.decryptograph(msg);
		buffer.put(msg.hashCode(), msg);
		sendNotification(msg, "invU");
	}

	/**
	 * Decifra o bloco de dados e a chave cifrada usando a chave privada do destinat�rio.
	 * @param pvk A chave privada.
	 * @param textoCifrado Os dados cifrados.
	 * @param chaveCifrada A chave cifrada.
	 * @return Os dados decifrados.
	 * @throws NoSuchAlgorithmException Algoritmo AES n�o dispon�vel na sua vers�o do JDK.
	 * @throws NoSuchPaddingException PKCS5Padding n�o dispon�vel na sua vers�o do JDK.
	 * @throws InvalidKeyException Se a chave passada for inv�lida.
	 * @throws IllegalBlockSizeException N�o deve ocorrer.
	 * @throws BadPaddingException Se houver um erro de decifra��o (chave incorreta ou texto
	 * cifrado incorreto, por exemplo)
	 * @throws InvalidAlgorithmParameterException N�o deve ocorrer.
	 */
	public byte[] decifra(PrivateKey pvk, byte[] textoCifrado,
			byte[] chaveCifrada) throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException,
			IllegalBlockSizeException, BadPaddingException,
			InvalidAlgorithmParameterException {
		byte[] textoDecifrado = null;

		// -- A) Decifrando a chave sim�trica com a chave privada
		Cipher rsacf = Cipher.getInstance("RSA");
		rsacf.init(Cipher.DECRYPT_MODE, pvk);
		byte[] chaveDecifrada = rsacf.doFinal(chaveCifrada);
		// -- B) Decifrando o texto com a chave sim�trica decifrada
		Cipher aescf = Cipher.getInstance("AES/CBC/PKCS5Padding");
		IvParameterSpec ivspec = new IvParameterSpec(new byte[16]);
		aescf.init(Cipher.DECRYPT_MODE,
				new SecretKeySpec(chaveDecifrada, "AES"), ivspec);
		textoDecifrado = aescf.doFinal(textoCifrado);

		return textoDecifrado;
	}

	/**
	 * Carrega a chave privada serializada de um arquivo.
	 * @param fPvk O arquivo com a chave privada serializada.
	 * @return A chave privada.
	 * @throws IOException Se n�o achar o arquivo, ou se houver algum problema
	 * @throws ClassNotFoundException O objeto contido no arquivo � de uma classe n�o presente
	 * neste projeto.
	 */
	public PrivateKey carregaChavePrivada(File fPvk) throws IOException,
			ClassNotFoundException {
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fPvk));
		PrivateKey ret = (PrivateKey) ois.readObject();
		ois.close();
		return ret;
	}

}
